﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Input;

using ActiproSoftware.Windows.Controls.Docking;

using Microsoft.Practices.Composite.Events;
using Microsoft.Practices.Composite.Regions;
using Microsoft.Practices.Composite.Presentation.Commands;
using Microsoft.Practices.Composite.Presentation.Events;
using Microsoft.Practices.Unity;

using Supremacy.Annotations;
using Supremacy.Game;

namespace SupremacyEditor
{
    public interface IEditorController
    {
        #region Events
        event EventHandler<DataEventArgs<IEditorItem>> LoadItem;
        event EventHandler ReloadModuleData;
        event EventHandler<DataEventArgs<IEditorItem>> SaveItem;
        #endregion

        #region Properties and Indexers
        IUnityContainer Container { get; }
        IEditorItem ActiveItem { get; }
        IGameDataModel GameData { get; }
        #endregion

        #region Methods
        void Close(IEditorItem item);
        void Open(IEditorItem item, bool select);
        void ReloadData();
        void Save(IEditorItem item);
        void Select(IEditorItem item);
        void Run();
        #endregion
    }

    public class EditorController : IEditorController
    {
        private readonly Dictionary<IEditorItem, EditorItemData> _itemDataLookup;
        private readonly IRegionManager _regionManager;
        protected readonly DelegateCommand<IEditorItem> OpenItemCommand;

        protected readonly DelegateCommand<object> SaveItemCommand;

        #region Constructors
        public EditorController(IUnityContainer container, IRegionManager regionManager, IGameDataModel gameData)
        {
            _regionManager = regionManager;
            _itemDataLookup = new Dictionary<IEditorItem, EditorItemData>();

            this.Container = container;
            this.ActiveItem = null;
            this.GameData = gameData;
            this.OpenItemCommand = new DelegateCommand<IEditorItem>(ExecuteOpenItemCommand) { IsActive = true };
            this.SaveItemCommand = new DelegateCommand<object>(ExecuteSaveItemCommand, CanExecuteSaveItemCommand) { IsActive = true };

            CommandManager.RequerySuggested += HandleItemChanged;

            EditorCommands.OpenItem.RegisterCommand(this.OpenItemCommand);

            EditorEvents.ItemClosed.Subscribe(HandleItemClosed, ThreadOption.PublisherThread);
        }

        public void Run()
        {
            _regionManager.Regions[RegionNames.Documents].ActiveViews.CollectionChanged += HandleActiveViewsChanged;
        }

        private void HandleActiveViewsChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.OldItems != null)
            {
                foreach (var itemWindow in e.OldItems.OfType<DocumentWindow>())
                {
                    var item = itemWindow.DataContext as IEditorItem;
                    if (item == null)
                        continue;
                    EditorItemData itemData;
                    if (!_itemDataLookup.TryGetValue(item, out itemData))
                        continue;
                    if (itemData.SaveCommand != null)
                        itemData.SaveCommand.IsActive = false;
                }
            }
            if (e.NewItems != null)
            {
                foreach (var itemWindow in e.NewItems.OfType<DocumentWindow>())
                {
                    var item = itemWindow.DataContext as IEditorItem;
                    if (item == null)
                        continue;
                    EditorItemData itemData;
                    if (!_itemDataLookup.TryGetValue(item, out itemData))
                        continue;
                    if (itemData.SaveCommand != null)
                        itemData.SaveCommand.IsActive = true;
                }
            }
        }

        private bool CanExecuteSaveItemCommand(object arg)
        {
            var selectedItemWindow =
                _regionManager.Regions[RegionNames.Documents].ActiveViews.FirstOrDefault() as DocumentWindow;
            if (selectedItemWindow == null)
                return false;
            var selectedItem = selectedItemWindow.DataContext as IEditorItem;
            if (selectedItem != null)
                return selectedItem.IsChanged;
            return false;
        }

        private void ExecuteSaveItemCommand(object arg)
        {
            var selectedItemWindow =
                _regionManager.Regions[RegionNames.Documents].ActiveViews.FirstOrDefault() as DocumentWindow;
            if (selectedItemWindow == null)
                return;
            var selectedItem = selectedItemWindow.DataContext as IEditorItem;
            if (selectedItem != null)
                Save(selectedItem);
            RequeryCommands();
        }

        private void HandleItemChanged(object sender, EventArgs e)
        {
            /*
             * If we're handling a message directly from an editor item, then we only need to
             * update the save command for that item.  Otherwise, we just requery all commands.
             */
            var item = sender as IEditorItem;
            if (item != null)
            {
                EditorItemData itemData;
                if (_itemDataLookup.TryGetValue(item, out itemData) && (itemData.SaveCommand != null))
                {
                    itemData.SaveCommand.RaiseCanExecuteChanged();
                    return;
                }
            }
            RequeryCommands();
        }

        protected void RequeryCommands()
        {
            this.OpenItemCommand.RaiseCanExecuteChanged();
            this.SaveItemCommand.RaiseCanExecuteChanged();
        }

        private void HandleItemClosed(IEditorItem item)
        {
            if (item == null)
                return;
            EditorItemData itemData;
            if (_itemDataLookup.TryGetValue(item, out itemData) && (itemData.SaveCommand != null))
                EditorCommands.SaveItem.UnregisterCommand(itemData.SaveCommand);
            _itemDataLookup.Remove(item);
            item.Changed -= HandleItemChanged;
            item.EndEdit();
        }

        private void ExecuteOpenItemCommand(IEditorItem item)
        {
            if (item == null)
                return;
            Open(item, true);
        }
        #endregion

        #region Methods
        public void LoadGameData() {}

        protected void OnLoadItem([NotNull] IEditorItem item)
        {
            if (item == null)
                throw new ArgumentNullException("item");
            if (LoadItem != null)
                LoadItem(this, new DataEventArgs<IEditorItem>(item));
        }

        protected void OnReloadModuleData()
        {
            if (ReloadModuleData != null)
                ReloadModuleData(this, EventArgs.Empty);
        }

        protected void OnSaveItem([NotNull] IEditorItem item)
        {
            if (item == null)
                throw new ArgumentNullException("item");
            if (SaveItem != null)
                SaveItem(this, new DataEventArgs<IEditorItem>(item));
        }
        #endregion

        #region IEditorController Members
        public IUnityContainer Container { get; private set; }
        public IEditorItem ActiveItem { get; private set; }
        public IGameDataModel GameData { get; private set; }

        public void Open(IEditorItem item, bool select)
        {
            EditorItemData itemData;

            if (!_itemDataLookup.TryGetValue(item, out itemData))
            {
                var editorType = EditorTypeAttribute.GetEditorType(item.GetType());

                itemData = new EditorItemData
                           {
                               Window = new DocumentWindow
                                        {
                                            DataContext = item,
                                        },
                               SaveCommand = new DelegateCommand<object>(
                                   ExecuteSaveItemCommand,
                                   CanExecuteSaveItemCommand) { IsActive = true }
                           };

                EditorCommands.SaveItem.RegisterCommand(itemData.SaveCommand);

                if (editorType != null)
                {
                    itemData.Window.Content = new ScrollViewer
                                              {
                                                  Content = Activator.CreateInstance(editorType),
                                                  HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled,
                                                  VerticalScrollBarVisibility = ScrollBarVisibility.Auto
                                              };
                }

                itemData.Window.SetBinding(
                    DockingWindow.TitleProperty,
                    "Header");

                _itemDataLookup[item] = itemData;

                item.Changed += HandleItemChanged;

                item.BeginEdit();

                _regionManager.Regions[RegionNames.Documents].Add(itemData.Window);
            }

            _regionManager.Regions[RegionNames.Documents].Activate(itemData.Window);
        }

        public void Save([NotNull] IEditorItem item)
        {
            if (item == null)
                throw new ArgumentNullException("item");
            if (!item.IsChanged)
                return;
            item.AcceptChanges();
            item.BeginEdit();
        }

        public void Select([NotNull] IEditorItem item)
        {
            if (item == null)
                throw new ArgumentNullException("item");
            EditorItemData itemData;
            if (!_itemDataLookup.TryGetValue(item, out itemData))
                return;
            if (itemData.Window == null)
                return;
            if (!itemData.Window.IsOpen)
                itemData.Window.Open();
            itemData.Window.Activate();
        }

        public void Close(IEditorItem item)
        {
            EditorItemData itemData;
            if (!_itemDataLookup.TryGetValue(item, out itemData))
                return;
            if (itemData.Window == null)
                return;
            itemData.Window.Close();
        }

        public void ReloadData()
        {
            while (GameContext.Current != null)
                GameContext.Pop();

            GameContext.Push(new GameContext());

            Debug.Assert(GameContext.Current != null);

            GameContext.Current.Tables = GameTables.Load();

            OnReloadModuleData();
        }

        public event EventHandler<DataEventArgs<IEditorItem>> SaveItem;
        public event EventHandler<DataEventArgs<IEditorItem>> LoadItem;
        public event EventHandler ReloadModuleData;
        #endregion

        #region Nested type: EditorItemData
        protected class EditorItemData
        {
            public IEditorItem Item { get; set; }
            public DocumentWindow Window { get; set; }
            public DelegateCommand<object> SaveCommand { get; set; }
        }
        #endregion
    }

    public static class EditorControllerExtensions
    {
        #region Methods
        public static void Open(this IEditorController controller, IEditorItem item)
        {
            controller.Open(item, true);
        }
        #endregion
    }
}